import {
  DataSource,
  CollectionViewer,
  SelectionChange,
  SelectionModel,
} from '@angular/cdk/collections';
import { TreeControl, FlatTreeControl } from '@angular/cdk/tree';
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Router } from '@angular/router';
import { NzMessageService } from 'ng-zorro-antd/message';
import { Observable, of, BehaviorSubject, merge } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { OrganizationService } from 'src/app/service/organization.service';
import { RoleService } from 'src/app/service/role.service';

interface FlatNode {
  expandable: boolean;
  id: any;
  label: string;
  level: number;
  loading?: boolean;
  type?: string;
  me?: any;
}

function getChildren(
  node: FlatNode,
  organizationService: OrganizationService
): Observable<FlatNode[]> {
  //children is organization

  return of([]);
}

class DynamicDatasource implements DataSource<FlatNode> {
  private flattenedData: BehaviorSubject<FlatNode[]>;
  private childrenLoadedSet = new Set<FlatNode>();

  constructor(
    private treeControl: TreeControl<FlatNode>,
    initData: FlatNode[],
    private organizationService: OrganizationService
  ) {
    this.flattenedData = new BehaviorSubject<FlatNode[]>(initData);
    treeControl.dataNodes = initData;
  }

  connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
    const changes = [
      collectionViewer.viewChange,
      this.treeControl.expansionModel.changed.pipe(
        tap((change) => this.handleExpansionChange(change))
      ),
      this.flattenedData,
    ];
    return merge(...changes).pipe(
      map(() => this.expandFlattenedNodes(this.flattenedData.getValue()))
    );
  }

  expandFlattenedNodes(nodes: FlatNode[]): FlatNode[] {
    const treeControl = this.treeControl;
    const results: FlatNode[] = [];
    const currentExpand: boolean[] = [];
    currentExpand[0] = true;

    nodes.forEach((node) => {
      let expand = true;
      for (let i = 0; i <= treeControl.getLevel(node); i++) {
        expand = expand && currentExpand[i];
      }
      if (expand) {
        results.push(node);
      }
      if (treeControl.isExpandable(node)) {
        currentExpand[treeControl.getLevel(node) + 1] =
          treeControl.isExpanded(node);
      }
    });
    return results;
  }

  handleExpansionChange(change: SelectionChange<FlatNode>): void {
    if (change.added) {
      change.added.forEach((node) => this.loadChildren(node));
    }
  }

  loadChildren(node: FlatNode): void {
    if (this.childrenLoadedSet.has(node)) {
      return;
    }
    node.loading = true;
    getChildren(node, this.organizationService).subscribe((children) => {
      if (node.type === 'ORG') {
        this.organizationService
          .getChildren(node.id)
          .subscribe((organizations) => {
            organizations = organizations.map((organization) => {
              return {
                id: organization.id,
                label: organization.name,
                level: node.level + 1,
                expandable:
                  organization.childCount>0 || organization.roleCount > 0,
                me: organization,
                type: 'ORG',
              };
            });

            node.loading = false;
            const flattenedData = this.flattenedData.getValue();
            const index = flattenedData.indexOf(node);
            if (index !== -1) {
              flattenedData.splice(index + 1, 0, ...organizations);
              this.childrenLoadedSet.add(node);
            }
            this.flattenedData.next(flattenedData);
          });

        // get root roles
        this.organizationService
          .getRoles(node.id)
          .subscribe((roles) => {
            roles = roles.map((role) => {
              return {
                id: role.id,
                label: role.role.name,
                level: node.level + 1,
                expandable: role.childCount>0,
                me: role.role,
                type: 'ROLE',
              };
            });

            node.loading = false;
            const flattenedData = this.flattenedData.getValue();
            const index = flattenedData.indexOf(node);
            if (index !== -1) {
              flattenedData.splice(index + 1, 0, ...roles);
              this.childrenLoadedSet.add(node);
            }
            this.flattenedData.next(flattenedData);
          });
      }

      // get org role
      if (node.type === 'ROLE') {
        this.organizationService
          .getRoleChildren(node.id.organizationId, node.id.roleId)
          .subscribe((roles) => {
            roles = roles.map((role) => {
              return {
                id: role.id.id,
                label: role.organizationRole.role.name,
                level: node.level + 1,
                expandable: role.organizationRole.childCount>0,
                me: role.organizationRole.role,
                type: 'ROLE',
              };
            });

            node.loading = false;
            const flattenedData = this.flattenedData.getValue();
            const index = flattenedData.indexOf(node);
            if (index !== -1) {
              flattenedData.splice(index + 1, 0, ...roles);
              this.childrenLoadedSet.add(node);
            }
            this.flattenedData.next(flattenedData);
          });
      }
    });
  }

  disconnect(): void {
    this.flattenedData.complete();
  }
}

@Component({
  selector: 'app-role-tree',
  templateUrl: './role-tree.component.html',
  styleUrls: ['./role-tree.component.css'],
})
export class RoleTreeComponent implements OnInit {
  modelName?: string;
  constructor(
    private roleService: RoleService,
    private router: Router,
    private fb: FormBuilder,
    private message: NzMessageService,
    private organizationService: OrganizationService
  ) {
    this.roleService.getModelName().subscribe((res) => {
      this.modelName = res.modelName;
    });
  }

  selectListSelection = new SelectionModel<FlatNode>(false);

  TREE_DATA: FlatNode[] = [];

  ngOnInit(): void {
    // get root oganization
    this.organizationService.getChildren('').subscribe((organizations) => {
      organizations = organizations.map((organization) => {
        return {
          id: organization.id,
          label: organization.name,
          level: 0,
          expandable:
            organization.childCount > 0 || organization.roleCount > 0,
          me: organization,
          type: 'ORG',
        };
      });
      this.TREE_DATA.push(...organizations);
      // check if the org have roles
      this.dataSource = new DynamicDatasource(
        this.treeControl,
        this.TREE_DATA,
        this.organizationService
      );
    });
  }

  treeControl = new FlatTreeControl<FlatNode>(
    (node) => node.level,
    (node) => node.expandable
  );

  dataSource: any;
  hasChild = (_: number, node: FlatNode) => node.expandable;
}
